package com.OtherExercises._3_18_考试;

import java.io.*;
import java.util.ArrayList;
import java.util.Scanner;

public class _3_18_考试 {
    public static void main(String[] args) throws IOException {
        /*
        1.String是不是基本数据类型?简述String类的若干常用方法

          String不是基本数据类型.
          str.length() --- 获取字符串长度
          str.charAt() --- 返回字符串中指定下标处的字符
          str.toCharArray() --- 把字符串对象转换成字符数组
          str.equals(str1)) --- 判断两个字符串内容是否相同
          str.equalsIgnoreCase(str1)) --- 无视大小写比较字符串内容
          str.substring(int beginIndex, int endIndex) --- 从起始截取到结尾下标,包前不包后
          str.substring(int beginIndex) --- 从起始截取到字符串末尾
          str.replace("A", "B") --- 使用replace函数可以用指定的字符串替换指定的字符串并返回新的字符串
          str.contains("A") --- 使用contains方法判断字符串中是否含有指定字符串
          str.startsWith("A") --- 使用startsWith方法判断字符串是否是以指定字符串开头的
          str.endsWith("A") --- 判断字符串是否以某个字符串结尾
          str.split("A") --- 使用split函数将字符串按照指定的字符串作为分割标志,将原字符串分割成多个字符串,返回结果是String[]
          str.trim() --- 返回一个字符串，其值是原字符串，除去任何前导和尾随空格，或者如果该字符串没有前导或尾随的空格，则为原字符串
          "A".compareTo("B") --- 按照ASCII码值比较字符串大 "A" > "B" 返回正整数 "A" < "B" 返回负整数 "A" = "B" 返回整数0
          String.valueOf(xxx) --- 将其他基本类型转成String类型
        */

        /*
        2.String和StringBuilder的区别

        StringBuilder代表可变字符串对象,相当于一个容器,它里面装的字符串是可以改变的,专门用于操作字符串,与String类相比更适合做字符串的修改操作,效率更高,代码更简洁, 当需要频繁的添加修改字符串建议使用StringBuilder

        String对象是不可变的,每次改变字符串对象实际上是新产生的字符串对象了,以"abc"方式写出的字符串,会在堆内存中的字符串常量池中存储,且相同内容的字符串只存储一份,目的是节约内存;但是通过new方式创建的字符串对象,每new一次都会产生一个新的对象放在堆内存中

        */

        /*
        3.简述Java的集合体系结构

        集合分为两大阵营:单列集合Collection 与 双列(键值对)集合Map
        Collection代表单列集合,每个元素(数据)包含一个值
        Map代表双列集合,每个元素包含两个值(键值对)

        Collection<E>本身是一个泛型接口,
        Collection<E>下面有两个常用的子接口List<E>和Set<E>,
        List<E>下面有实现类ArrayList<E>,LinkedList<E>
        Set<E>下面有实现类HashSet<E>,TreeSet<E>
        HashSet<E>下面有实现类LinkedHashSet<E>

        List系列集合(ArrayList<E>,LinkedList<E>)添加的元素是有序的(队列),可重复的(元素可重复),有索引的(支持随机访问)

        Set系列集合中
        HashSet<E> 添加的元素是无序的,不重复的,无索引的 ,
        LinkedHashSet<E> 中的元素是有序的,不重复的,无索引的,
        TreeSet<E> 按照大小默认升序排序,不重复的,无索引的

         */

        /*
        4.ArrayList和LinkedList的区别

        ArrayList底层是基于"数组"实现的
        因此根据索引随机访问数据,是非常快的
        删除效率低,因为需要挨个挪动数据
        添加效率也低,可能需要把很多数据后移,也可能需要数组扩容
        总结为:查询快,增删慢

        ArrayList集合的底层原理:
        ①利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组
        ②添加add第一个元素,底层会创建一个新的长度为10的数组
        ③当存满了,存第11个数据时,会新建一个数组扩容到1.5倍(即15),把原数据迁移到新数组,再存第11个数据
        ④如果一次性添加addAll多个元素,1.5倍还放不下,则新建数值的长度以实际为准,如原长度为10,新添加11个数据,则新数组长度为21
        如果使用带参的构造创建ArrayList集合对象,将根据参数元素的个数初化内部数组长度;再add单个元素时超出容量,会扩容到1.5倍;如果addAll多个元素超出了1.5倍扩容后的容量,则以当前实际总长度进行扩容

        LinkedList底层是基于"双链表"实现的
        链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址
        单链表的特点:查询慢,需要从头开始遍历;但是增删相对快
        双链表的特点:对首尾元素进行增删改查的速度是极快的
        而LinkedList是基于双链表实现的,查询慢,需要从头开始遍历,但是增删相对快,对首尾元素进行增删改查的速度是极快的
        所以LinkedList有很多首尾操作的特有方法
        常常使用LinkedHashSet来构建栈和队列的结构

        */

        /*
        5.简述HashMap的底层原理

        HashMap跟HashSet的底层原理是一模一样的,都是基于哈希表(JDK8前数组+链表,JDK8之后数组+链表+红黑树)实现的
        实际上,Set系列底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已

        HashSet集合底层基于哈希表(即数组下挂链表/红黑树)实现,哈希表是一种增删改查数据,性能都较好的数据结构,因为只需要拿着哈希值对数组长度求余就可以随机访问,而插入删除直接下挂或者置为null即可
         在JDK8之前,哈希表 = 数组 + 链表
         JDK8之后,哈希表 = 数组 + 链表 + 红黑树
         Set<String> set = new HashSet<>();
         set.add("数据1");
         创建HashSet对象后,第一次添加数据对象时,
         ①默认创建一个长度为16的数组,默认加载因子为0.75,数组名table
         ②使用元素的"哈希值"对"数组长度求余"计算出应存入的位置
         ③判断当前位置是否为null,如果是null则直接存入
         ④如果求余所得的位置不为null,表示已经有元素了,则调用equals方法进行比较,如果相等,则直接去重不存,如果不相等,则存入数组.
         具体存放而言,JDK8之前,将新元素放到当前位置,原来的老元素用链表下挂,JDK8之后,新元素会直接用链表或红黑树直接挂在老元素下面

         随着数据添加,下挂链表会过过长,导致查询性能降低,需要进行数组扩容,默认加载因子是0.75,即数组占用到75%(16 * 0.75 = 12)后就会扩容,并不会等到完全填满16个数组位置才扩容,因为占满12个坑可能就已经下挂了很长的链表了,扩容会扩容到原数组的2倍,再把原数组中的数据全部转移到新数组中,但是即使是扩容数组,依然可能出现某一个数组元素下挂很长的链表导致查询效率降低,于是从JDK8开始,当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树,红黑树是一颗二叉树,哈希值小的元素往根结点左边放,哈希值大的元素往根结点右边放,基于二分查找,查询效率大大提高

        总体而言,HashMap集合是一种增删改查数据,性能都较好的集合,但它是无序的,不能重复的,没有索引支持的
        HashMap的键依赖hashCode方法和equals方法保证键的唯一,也就是说如果集合中的元素是自定义类型,那么想要去重需要重写hashCode方法和equals方法(重写hashCode使得根据元素内容不同对象产生相同哈希值,从而在底层数组中位于同一元素位置,再通过重写equals来判断自定义类型对象内容是否相同,相同就不添加到哈希表中,从而达到自定义类型去重的目的)
         */

        /*
        6.简述Map集合的遍历方式

        map集合的遍历方式①
        先获取map集合全部的键,再通过遍历键来找值
        public Set<K> ketSet(); 获取map集合的全部键
        public V get(Object key) 根据键获取对应值
        Set<String> keys = map.keySet();
        for (String key : keys) {
            Double value = map.get(key);
            System.out.println(key + "===>" + value);
        }

         map集合的遍历方式②
         把"键值对"看成整体进行遍历
         需要使用Map提供的方法:
         Set<Map,Entry<K,V>> entrySet() --- 该方法会把所有"键值对"封装成一个个Map.Entry<K,V>实现类的对象,并加入一个set集合中,Map.Entry<K,V>是Map的一个内部接口
         Map.Entry提供的方法:
         K getKey() --- 获取键
         V getValue() --- 获取值
         Set<Map.Entry<String, Double>> entries = map.entrySet();
         for (Map.Entry<String, Double> entry : entries) {
            String key = entry.getKey();
            Double value = entry.getValue();
            System.out.println(key + "==>" + value);
         }

         map集合的遍历方式③
         forEach+Lambda表达式实现遍历集合
         map.forEach(new BiConsumer<String, Double>() {
            @Override
            public void accept(String s, Double v) {
                System.out.println(s + "=" + v);
            }
         });
         可以使用Lambda表达式简化为
         map.forEach((key, value) -> System.out.println(key + "=" + value));

         */

        /*
        7.简述单例设计模式分类及各自特点

        单例设计模式分为"饿汉模式"和"懒汉模式"

        饿汉模式:无论将来是否使用到工具类中的唯一实例,都先创建,以空间换时间.
        1.private修饰构造方法:不让类外创建对象(不能new)
        2.private修饰静态变量:存放唯一对象,同时就在堆中创建唯一实例,不管以后用不用
        3.public修饰静态方法:使得类外无须实例化也可以用类名调用,该方法用来返回类内部创建的唯一实例
        类外无法创建实例,只在类内创建唯一实例,外部可以通过类内提供的函数获得唯一实例,可以使用唯一实例调用单例类内的其他函数,一般用于实现工具类,可以达到节省内存的目的,始终只存在唯一的实例,只开一次空间
        类内的其他静态函数可能并不需要实例来调用,直接类名调用即可,导致饿汉模式中提前创建的唯一实例浪费了

        与 饿汉模式 相对的是 懒汉模式,
        懒汉模式:以时间换空间,创建对象浪费时间,但是不会导致浪费内存
        首次调用获取实例函数时才会开空间,获取实例,之后再次调用将不会创建实例,即使类内其他静态函数直接通过类名调用,也不会导致浪费创建唯一实例的空间,但是代价是下次需要使用唯一实例时,需要判断然后再创建才能使用,以时间换空间

         */

        /*
        8.简述JDK8中目前你使用过的新特性及作用
        [
            接口中私有及default默认方法:解决接口升级问题
            Lambda:简化特殊匿名内部类对象传递操作︰适用于有且只有一个抽象方法的接口
            Java中该类型接口称为功能性接口,存在于function包下
            方法引用:对lambda进行进一步优化
            类名::静态方法名，对象名::成员方法名，类名::new
            localDateTime
            Stream流:简化集合数据操作
            使用红黑树优化JDK7哈希表
        ]

        JDK8开始提供了3个更方便的操作日期时间的类
        LocalDate : 代表本地日期(年,月,日,星期)
        LocalTime : 代表本地时间(时,分,秒,纳秒)
        LocalDateTime : 代表本地日期,时间(年,月,日,星期,时,分,秒,纳秒)
        用于替代Calendar
        1.设计更合理,功能更丰富,使用更方便
        2.都是不可变对象,修改后会返回新的时间对象,不会丢失最开始的时间
        3.线程安全
        4.能精确到毫秒,纳秒

        时区 与 带时区的日期时间 ZoneId ZoneDateTime
        时区通常用 洲名/城市名 表示 : Asia/Shanghai Asia/Chongqing America/New_York

        Instant指时间线上的某个时刻/时间戳 用于替代Date类
        通过Instant的对象可以拿到此刻的时间,该时间由两部分组成:
        从1970-01-01 00:00:00 开始走到此刻的总秒数 + 不够一秒的纳秒数
        Instant精确到了纳秒级

        DateTimeFormatter : 格式化器,用于时间的格式化,解析,用于替代SimpleDateFormat,是线程安全的

        Period 用于计算两个 LocalDate对象 相差的年数,月数,天数 , 无法计算LocalTime对象相差的时分秒毫秒纳秒,要使用Duration解决

        Duration 代表持续时间,可以用于计算两个时间对象相差的天数,小时数,分数,秒数,纳秒数,支持LocalTime,LocalDateTime,Instant等时间

        lambda表达式作为JDK8的新特性,作用是用来简化特殊匿名内部类参数传递
        作用范围:适用于有且只有一个 "抽象方法" 的 "接口" ,即函数式接口通常含有@FunctionalInterface注解
        Java中提供了一整套function接口,都符合lambda表达式的使用条件
        使用技巧:在传递接口匿名内部类位置 alt+回车
        格式解析:
        lambda表达式是函数式编程思想
        数据输入 -> 函数(实参列表) 执行完毕获取执行结果
        ():是重写方法的形式参数列表 ()表示空参,具体变量直接写名字即可
        ->:数据传输
        函数(实参列表):接口中唯一抽象方法重写的方法体

        方法引用是对lambda表达式的进一步优化
        Lambda:数据->功能
        方法引用:只关注功能,省去了数据,因为数据必定会传给功能

        Stream流操作数据的形式:
        流水线中可以对数据进行处理,例如:过滤,限制个数...
        好处可以提高操作数据的性能
        通过 集合对象名.stream()

        Stream流中间方法
        在Stream流中对数据进行操作的方法
        方法执行完毕后返回的是处理完毕后数据的Stream流对象

         */


        /**
         * 二．代码题
         * 北京地铁管理系统
         * 针对北京地铁项目，需要开发一个信息化管理系统。请根据公司现有业务及需求完成该系统的初始设计。
         *
         * 【需求描述】
         * （1）记录管理人员信息。员工信息包括工号、姓名、性别和一个手机号，工号唯一标识每位员工。
         * （2）记录地铁站信息。地铁站信息包括地铁站编号、地铁站名称、所属路线、停留时间、起始运营时间、停止运营时间,系统内部用地铁站编号唯一标识每个地铁站。
         *
         * 【结构设计】
         * 管理人员（工号，姓名，性别，手机号）
         * 地铁站（地铁站编号，地铁站名称，所属路线）
         *
         * 【问题1】
         * 请根据业务需求及结构，设计出管理人员类（Manager）和地铁站类(Subway)
         * 要求：
         * 1.包括所涉及的属性
         * 2.要生成getter()&amp;setter()方法
         * 3.要包含无参和带全部参数的构造方法
         * 4.要有相应的toString()方法，用于输出相关属性的值
         *
         * 【问题2】
         * 运行程序,提示管理员进行登录,根据输入的工号和姓名判断是否登录成功,登录成功,进入下一步,登录失败,给出提示,结束程序
         * 系统中现有管理人员信息如下：
         * 10001,小王,男,13112345678
         * 10002,老于,男,13212345678
         * 10003,老方,男,13312345678
         * 保存在当前项目模块下的managers.txt文件中
         * 系统中现有地铁站信息如下:
         * s3002,南锣鼓巷,6号线
         * s5016,王府井,1号线
         * s2026,西直门,4号线
         * 保存在当前项目模块下的subways.txt文件中
         * 要求：
         * 2.1. 运行程序,将managers.txt文件中的管理人员信息读取到集合中
         * 2.1.1. 将读取到的一行数据切割成字符串数组
         * 2.1.2. 将数组中的元素封装到Manager对象中
         * 2.1.3. 将Manager对象添加到集合中
         * 2.2. 将subways文件中的地铁站信息读取到另一个集合中
         * 2.3. 给出提示,获取管理人员输入的工号和姓名
         * 2.4. 判断管理人员信息集合中是否包含管理人员输入的工号和姓名,给出对应的登录结果
         *
         * 【问题3】
         * 当管理人员登录成功后,给出选项提示,根据管理人员的选择,执行后续的操作
         * 要求：
         * 3.1. 给出提示: 1.查看地铁站 2.添加地铁站
         * 3.2. 获取管理人员输入的选择
         * 3.3. 根据管理人员的选择,执行对应的操作
         *
         * 【问题4】
         * 当管理人员选择1时,执行对应的查看地铁站操作
         * 要求：
         * 4.1. 判断地铁站集合中是否有地铁站信息
         * 4.2. 如果没有地铁站信息,给出提示
         * 4.3. 如果有地铁站信息,遍历集合,打印每个地铁站信息
         *
         * 【问题5】
         * 当管理人员选择2时,执行对应的添加地铁站操作
         * 要求：
         * 5.1.依次给出提示,获取管理人员输入的地铁站信息,包括地铁站编号，地铁站名称，所属路线
         * 5.2. 将管理人员输入的地铁站信息封装成对象,存储到集合中
         * 5.3. 将集合中的地铁站信息更新到文件中,再次打开文件时,应该包含管理人员刚才录入进去的地铁站信息,程序结束
         */


        /*【问题1】
         * 请根据业务需求及结构，设计出管理人员类（Manager）和地铁站类(Subway)
         * 要求：
         * 1.包括所涉及的属性
         * 2.要生成getter()&amp;setter()方法
         * 3.要包含无参和带全部参数的构造方法
         * 4.要有相应的toString()方法，用于输出相关属性的值*/

        /*【问题2】
         * 运行程序,提示管理员进行登录,根据输入的工号和姓名判断是否登录成功,登录成功,进入下一步,登录失败,给出提示,结束程序
         * 系统中现有管理人员信息如下：
         * 10001,小王,男,13112345678
         * 10002,老于,男,13212345678
         * 10003,老方,男,13312345678
         * 保存在当前项目模块下的managers.txt文件中
         * 系统中现有地铁站信息如下:
         * s3002,南锣鼓巷,6号线
         * s5016,王府井,1号线
         * s2026,西直门,4号线
         * 保存在当前项目模块下的subways.txt文件中
         * 要求：
         * 2.1. 运行程序,将managers.txt文件中的管理人员信息读取到集合中
         * 2.1.1. 将读取到的一行数据切割成字符串数组
         * 2.1.2. 将数组中的元素封装到Manager对象中
         * 2.1.3. 将Manager对象添加到集合中
         * 2.2. 将subways文件中的地铁站信息读取到另一个集合中
         * 2.3. 给出提示,获取管理人员输入的工号和姓名
         * 2.4. 判断管理人员信息集合中是否包含管理人员输入的工号和姓名,给出对应的登录结果*/

        //2.1. 运行程序,将managers.txt文件中的管理人员信息读取到集合中
        ArrayList<String> managers = new ArrayList<>();
        BufferedReader bufferedReader = new BufferedReader(new FileReader("helloworld\\src\\managers.txt"));
        String line;
        while ((line = bufferedReader.readLine()) != null) {//注意:会把换行符舍弃
//            System.out.println(line);
            managers.add(line);
        }
        bufferedReader.close();
//        System.out.println(managers);

        //2.1.1. 将读取到的一行数据切割成字符串数组
        //2.1.2. 将数组中的元素封装到Manager对象中
        //2.1.3. 将Manager对象添加到集合中
        ArrayList<Manager> managerList = new ArrayList<>();
        for (String manager : managers) {
            String[] strings = manager.split(",");
            //数组: 10001 小王 男 13112345678
            Manager aManager = new Manager(strings[0], strings[1], strings[2], strings[3]);
//            System.out.println(aManager);
            managerList.add(aManager);
        }
//        System.out.println(managerList);

        //2.2. 将subways文件中的地铁站信息读取到另一个集合中
        ArrayList<Subway> subways = new ArrayList<>();
        bufferedReader = new BufferedReader(new FileReader("helloworld\\src\\subway.txt"));
        while ((line = bufferedReader.readLine()) != null) {
            String[] strings = line.split(",");
            Subway subway = new Subway(strings[0], strings[1], strings[2]);
            subways.add(subway);
        }
        bufferedReader.close();
//        System.out.println(subways);

        //2.3. 给出提示,获取管理人员输入的工号和姓名
        //2.4. 判断管理人员信息集合中是否包含管理人员输入的工号和姓名,给出对应的登录结果
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入工号:");
        String id = sc.nextLine();
        System.out.println("请输入姓名:");
        String name = sc.nextLine();

        boolean flag = false;
        for (Manager manager : managerList) {
            if (manager.getId().equals(id) && manager.getName().equals(name)) {
                flag = true;
                break;
            }
        }

        if (flag) {
            System.out.println("登陆成功!!!");
        } else {
            System.out.println("登录失败!!!");
            System.exit(0);
        }


        /*【问题3】
         * 当管理人员登录成功后,给出选项提示,根据管理人员的选择,执行后续的操作
         * 要求：
         * 3.1. 给出提示: 1.查看地铁站 2.添加地铁站
         * 3.2. 获取管理人员输入的选择
         * 3.3. 根据管理人员的选择,执行对应的操作*/
        System.out.println("1.查看地铁站");
        System.out.println("2.添加地铁站");
        System.out.println("请选择:");
        String temp = sc.next();
        if ("1".equals(temp)) {
            //查看地铁站
            /*【问题4】
             * 当管理人员选择1时,执行对应的查看地铁站操作
             * 要求：
             * 4.1. 判断地铁站集合中是否有地铁站信息
             * 4.2. 如果没有地铁站信息,给出提示
             * 4.3. 如果有地铁站信息,遍历集合,打印每个地铁站信息*/
            if (subways.isEmpty()) {
                System.out.println("地铁站集合是空的!");
            } else {
                for (Subway subway : subways) {
                    System.out.println(subway.toString());//已经重写toString
                }
            }

        }
        if ("2".equals(temp)) {
            //添加地铁站
            /*【问题5】
             * 当管理人员选择2时,执行对应的添加地铁站操作
             * 要求：
             * 5.1.依次给出提示,获取管理人员输入的地铁站信息,包括地铁站编号，地铁站名称，所属路线
             * 5.2. 将管理人员输入的地铁站信息封装成对象,存储到集合中
             * 5.3. 将集合中的地铁站信息更新到文件中,再次打开文件时,应该包含管理人员刚才录入进去的地铁站信息,程序结束*/
            System.out.println("请输入地铁站信息,");
            System.out.println("地铁站编号:");
            String stationNumber = sc.next();
            //查重
            for (Subway subway : subways) {
                if (subway.getStationNumber().equals(stationNumber)) {
                    throw new IllegalArgumentException("地铁站编号重复!");
                }
            }
            System.out.println("地铁站名称:");
            String stationName = sc.next();
            System.out.println("所属路线:");
            String route = sc.next();
            Subway subway = new Subway(stationNumber, stationName, route);
            subways.add(subway);
//            System.out.println(subways);
            BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("helloworld\\src\\subway.txt", true));
            bufferedWriter.newLine();
            bufferedWriter.write(subway.getStationNumber() + "," + subway.getStationNumber() + "," + subway.getRoute());
            bufferedWriter.close();
        }
    }
}

class Subway {
    private String stationNumber;//地铁站编号,唯一标识
    private String stationName;//地铁站名称
    private String route;//所属路线

    public Subway() {
    }

    public Subway(String stationNumber, String stationName, String route) {
        this.stationNumber = stationNumber;
        this.stationName = stationName;
        this.route = route;
    }

    @Override
    public String toString() {
        return "地铁站编号='" + stationNumber + '\n' +
                "地铁站名称='" + stationName + '\n' +
                "所属路线='" + route + '\n';
    }


    public String getStationNumber() {
        return stationNumber;
    }

    public void setStationNumber(String stationNumber) {
        this.stationNumber = stationNumber;
    }

    public String getStationName() {
        return stationName;
    }

    public void setStationName(String stationName) {
        this.stationName = stationName;
    }

    public String getRoute() {
        return route;
    }

    public void setRoute(String route) {
        this.route = route;
    }
}

class Manager {
    private String id;//工号,唯一标识
    private String name;//姓名
    private String gender;//性别
    private String phoneNumber;//手机号

    public Manager() {
    }

    public Manager(String id, String name, String gender, String phoneNumber) {
        this.id = id;
        this.name = name;
        this.gender = gender;
        this.phoneNumber = phoneNumber;
    }

    @Override
    public String toString() {
        return "Manager{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", phoneNumber='" + phoneNumber + '\'' +
                '}';
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
}